#if !defined(CUDA_COMMON_H_)
#define CUDA_COMMON_H_
#include <cuda_runtime.h>

#include <cstdint>
#include <memory>
#include <sstream>
namespace autodrive {
namespace perception {

class CUDATimer final {
 public:
  CUDATimer(cudaStream_t stream) noexcept(false);
  CUDATimer(CUDATimer&&) = delete;
  CUDATimer(const CUDATimer&) = delete;
  CUDATimer& operator=(CUDATimer&&) = delete;
  CUDATimer& operator=(const CUDATimer&) = delete;
  ~CUDATimer();

 public:
  float Elapsed() const noexcept(false);

 private:
  /// @brief cuda event start
  cudaEvent_t start_;
  /// @brief cuda event stop
  cudaEvent_t stop_;
  /// @brief stream
  cudaStream_t stream_;
};

using CUDAEventPtr = std::shared_ptr<cudaEvent_t>;
/// @brief cuda pinned memory
struct CUDAPinnedMemory {
  /// @brief host memory
  void* host;
  /// @brief dev memory
  void* dev;
};

using CUDAPinnedMemoryPtr = std::shared_ptr<CUDAPinnedMemory>;

#define check_cuda_error(err)                                   \
  do {                                                          \
    if (cudaSuccess != err) {                                   \
      std::stringstream ss;                                     \
      auto const& errStr{cudaGetErrorString(err)};              \
      ss << "[CUDA ERROR] at: " << __FILE__ << ": " << __LINE__ \
         << ", code=" << err << ", " << errStr;                 \
      throw std::runtime_error(ss.str());                       \
    }                                                           \
  } while (false)

/// @brief set cuda device
/// @param device device index
void set_cuda_device(std::int32_t device, std::ostream& info) noexcept(false);

static auto StreamDeleter = [](cudaStream_t* pStream) {
  if (pStream) {
    auto cuRet{cudaStreamDestroy(*pStream)};
    check_cuda_error(cuRet);
    delete pStream;
  }
};

inline std::unique_ptr<cudaStream_t, decltype(StreamDeleter)>
make_cuda_stream() noexcept(false) {
  std::unique_ptr<cudaStream_t, decltype(StreamDeleter)> pStream(
      new cudaStream_t, StreamDeleter);
  auto cuRet{cudaStreamCreate(pStream.get())};
  if (cuRet != cudaSuccess) {
    pStream.reset(nullptr);
  }
  check_cuda_error(cuRet);
  return pStream;
}

/// @brief make cuda event
/// @return cuda event pointer
CUDAEventPtr make_cuda_event() noexcept(false);

/// @brief make cuda memory with pinned
/// @param size size
/// @return <hostMem, devMem>
CUDAPinnedMemoryPtr make_cuda_pinned_mem(std::int32_t size) noexcept(false);

/// @brief pin host memory to cuda memory
/// @param hostAddr
/// @param size
/// @param devAddr
void pin_cuda_memory(void* host_addr, std::int32_t size,
                     void** dev_addr) noexcept(false);

}  // namespace perception
}  // namespace autodrive

#endif  // CUDA_COMMON_H_
